/*
*  Copyright (c) 2006-2020, Chukie
*
*  SPDX-License-Identifier: Apache-2.0
*
*  @file     : cellular_ppp.c
*
*  @brief    :
*  Website   : https://gitee.com/open-embedded
*
*
*  Change Logs:
*  Date           Author          Notes
*  2020-06-23     zhaocj       The first version
*/
/* Includes -----------------------------------------------------------------*/
#include <cellular.h>

#include <stdio.h>
#include "lwip/dns.h"
#define DBG_TAG "PPPD"
#define DBG_LVL DBG_LOG
#include <cellular_log.h>
/* Private typedef ----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
/* Extern variables ---------------------------------------------------------*/
extern rt_slist_t device_list;
/* Private function prototypes ----------------------------------------------*/
#define device_list_isempty()        rt_slist_isempty(&device_list)
#define device_list_foreach(node)    for ((node) = rt_slist_first(&device_list); (node); (node) = rt_slist_next(node))
#define device_list_append(c)        rt_slist_append(&device_list, &(c)->list)
#define device_list_remove(c)        rt_slist_remove(&device_list, &(c)->list)
#define device_list_entry(node)      rt_slist_entry(node, struct cellular_device, list)

#ifndef RT_USING_ULOG
    #define log_hex(tag,width,buf,size)
#endif /*RT_USING_ULOG*/

#ifdef DEBUG_DUMP
    #define PPPD_DUMP(data,len)                     log_hex(DBG_TAG, 0, (uint8_t *)data, len)
#else
    #define PPPD_DUMP(data,len)
#endif

/**
 * @brief device receive indicate callback
 *
 * @param device
 * @param size
 * @return rt_err_t
 */
static rt_err_t device_rx_ind(rt_device_t device, rt_size_t size)
{
    rt_slist_t *node;
    cellular_ppp_t p;
    if (!device)
    {
        LOG_E("device is null!");
        return -RT_EINVAL;
    }
    if (device_list_isempty())
    {
        return -RT_ENOSYS;
    }
    device_list_foreach(node)
    {
        p = &(device_list_entry(node)->ppp);
        if (((uintptr_t)p->device) == ((uintptr_t)device))
        {
            rt_sem_release(&p->rx_sem);
        }
    }

    return RT_EOK;
}

/**
 * @brief device send indicate callback
 *
 * @param device
 * @param size
 * @return rt_err_t
 */
static rt_err_t device_tx_ind(rt_device_t device,  void *buffer)
{
    rt_slist_t *node;
    cellular_ppp_t p;
    if (!device)
    {
        LOG_E("device is null!");
        return -RT_EINVAL;
    }

    if (device_list_isempty())
    {
        return -RT_ENOSYS;
    }

    device_list_foreach(node)
    {
        p = &(device_list_entry(node)->ppp);
        if (((uintptr_t)p->device) == ((uintptr_t)device))
        {
            rt_sem_release(&p->tx_sem);
        }
    }

    return RT_EOK;
}

int device_open(cellular_ppp_t instance)
{
    rt_base_t level;
    rt_uint16_t open_flag = RT_DEVICE_FLAG_RDWR;
    if (!instance->device)
    {
        LOG_E("PPPD device is unavailable!");
        return -RT_EIO;
    }

    level = rt_hw_interrupt_disable();
    if (instance->device->flag & RT_DEVICE_FLAG_DMA_TX)
    {
        open_flag |= RT_DEVICE_FLAG_DMA_TX;
        rt_device_set_tx_complete(instance->device, device_tx_ind);
    }
    else if (instance->device->flag & RT_DEVICE_FLAG_INT_TX)
    {
        open_flag |= RT_DEVICE_FLAG_INT_TX;
        rt_device_set_tx_complete(instance->device, NULL);
    }
    else
    {
        rt_device_set_tx_complete(instance->device, NULL);
    }


    if ((instance->device->flag & RT_DEVICE_FLAG_DMA_RX))
    {
        open_flag |= RT_DEVICE_FLAG_DMA_RX;
    }
    else if ((instance->device->flag & RT_DEVICE_FLAG_INT_RX))
    {
        open_flag |= RT_DEVICE_FLAG_INT_RX;
    }

    rt_device_set_rx_indicate(instance->device, device_rx_ind);
    rt_hw_interrupt_enable(level);
    while (instance->device->ref_count > 0)
    {
        LOG_W("The device %s may be in use,now close it!", instance->device->parent.name);
        rt_device_close(instance->device);
    }

    if (RT_EOK != rt_device_open(instance->device, open_flag))
    {
        LOG_E("Can't open device %s for pppd", instance->device->parent.name);
        level = rt_hw_interrupt_disable();
        rt_device_set_rx_indicate(instance->device, NULL);
        rt_device_set_tx_complete(instance->device, NULL);
        rt_hw_interrupt_enable(level);
        return -RT_EIO;
    }
    return RT_EOK;
}

int device_close(cellular_ppp_t instance)
{
    rt_base_t level;
    if (instance->device)
    {
        rt_device_close(instance->device);
        level = rt_hw_interrupt_disable();
        rt_device_set_rx_indicate(instance->device, NULL);
        rt_device_set_tx_complete(instance->device, NULL);
        rt_hw_interrupt_enable(level);
    }
    instance->device = RT_NULL;
    return RT_EOK;
}

#if LWIP_NETIF_STATUS_CALLBACK
static void netif_status_callback(struct netif *nif)
{
    LOG_I("PPPNETIF: %c%c%d is %s\n", nif->name[0], nif->name[1], nif->num,
          netif_is_up(nif) ? "UP" : "DOWN");
#if LWIP_IPV4
    LOG_I("IPV4: Host at %s ", ip4addr_ntoa(netif_ip4_addr(nif)));
    LOG_I("mask %s ", ip4addr_ntoa(netif_ip4_netmask(nif)));
    LOG_I("gateway %s\n", ip4addr_ntoa(netif_ip4_gw(nif)));
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
    LOG_I("IPV6: Host at %s\n", ip6addr_ntoa(netif_ip6_addr(nif, 0)));
#endif /* LWIP_IPV6 */
#if LWIP_NETIF_HOSTNAME
    LOG_I("FQDN: %s\n", netif_get_hostname(nif));
#endif /* LWIP_NETIF_HOSTNAME */
}
#endif /* LWIP_NETIF_STATUS_CALLBACK */
/**
 * @brief ppp callback
 *
 * @param pcb
 * @param err_code
 * @param ctx
 */
static void status_cb(ppp_pcb *pcb, int err_code, void *ctx)
{
    rt_slist_t *node;
    cellular_ppp_t p = NULL;
    if (device_list_isempty())
    {
        return;
    }
    device_list_foreach(node)
    {
        p = &(device_list_entry(node)->ppp);
        if (((uintptr_t)p->pcb) == ((uintptr_t)pcb))
        {
            break;
        }
    }
    if (!p)
    {
        return;
    }
    switch (err_code)
    {
    case PPPERR_NONE:
    {
#if LWIP_DNS
        const ip_addr_t *ns;
#endif /* LWIP_DNS */
        LOG_I("status_cb: PPP connected!");
#if PPP_IPV4_SUPPORT
        LOG_I("   our_ipaddr  = %s\n", ipaddr_ntoa(&pcb->netif->ip_addr));
        LOG_I("   his_ipaddr  = %s\n", ipaddr_ntoa(&pcb->netif->gw));
        LOG_I("   netmask     = %s\n", ipaddr_ntoa(&pcb->netif->netmask));
#if LWIP_DNS
        ns = dns_getserver(0);
        LOG_I("   dns1        = %s\n", ipaddr_ntoa(ns));
        ns = dns_getserver(1);
        LOG_I("   dns2        = %s\n", ipaddr_ntoa(ns));
#endif /* LWIP_DNS */
#endif /* PPP_IPV4_SUPPORT */
#if PPP_IPV6_SUPPORT
        //LOG_I("   our6_ipaddr = %s\n", ip6addr_ntoa(netif_ip6_addr(pppif, 0)));
#endif /* PPP_IPV6_SUPPORT */
        break;
    }
    case PPPERR_PARAM:
    {
        LOG_I("status_cb: Invalid parameter\n");
        break;
    }
    case PPPERR_OPEN:
    {
        LOG_I("status_cb: Unable to open PPP session\n");
        break;
    }
    case PPPERR_DEVICE:
    {
        LOG_I("status_cb: Invalid I/O device for PPP\n");
        break;
    }
    case PPPERR_ALLOC:
    {
        LOG_I("status_cb: Unable to allocate resources\n");
        break;
    }
    case PPPERR_USER:
    {
        LOG_I("status_cb: User interrupt\n");
        break;
    }
    case PPPERR_CONNECT:
    {
        LOG_I("status_cb: Connection lost\n");
        break;
    }
    case PPPERR_AUTHFAIL:
    {
        LOG_I("status_cb: Failed authentication challenge\n");
        break;
    }
    case PPPERR_PROTOCOL:
    {
        LOG_I("status_cb: Failed to meet protocol\n");
        break;
    }
    case PPPERR_PEERDEAD:
    {
        LOG_I("status_cb: Connection timeout\n");
        break;
    }
    case PPPERR_IDLETIMEOUT:
    {
        LOG_I("status_cb: Idle Timeout\n");
        break;
    }
    case PPPERR_CONNECTTIME:
    {
        LOG_I("status_cb: Max connect time reached\n");
        break;
    }
    case PPPERR_LOOPBACK:
    {
        LOG_I("status_cb: Loopback detected\n");
        break;
    }
    default:
    {
        LOG_I("status_cb: Unknown error code %d\n", err_code);
        break;
    }
    }
    LOG_D("p->pcb->phase: %d", p->pcb->phase);
    /*
     * This should be in the switch case, this is put outside of the switch
     * case for example readability.
     */
    if (err_code == PPPERR_NONE)
    {
        LOG_I("PPP(%c%c) link up!", pcb->netif->name[0], pcb->netif->name[1]);
        rt_event_send(&p->event, CELLULAR_PPP_EVENT_NET_UP);
        p->connected_ts = uptime();
#ifdef RT_USING_NETDEV
        cellular_netdev_sync(&p->netdev, pcb->netif);
        netdev_set_default(&p->netdev);
#endif /*RT_USING_NETDEV*/
    }
    /* ppp_close() was previously called, don't reconnect */
    else
    {
        LOG_W("PPP(%c%c) link down!", pcb->netif->name[0], pcb->netif->name[1]);
        rt_event_send(&p->event, CELLULAR_PPP_EVENT_NET_DOWN);
        /* ppp_free(); -- can't be called here */
    }
}

/*
 * PPPoS serial output callback
 *
 * ppp_pcb, PPP control block
 * data, buffer to write to serial port
 * len, length of the data buffer
 * ctx, optional user-provided callback context pointer
 *
 * Return value: len if write succeed
 */
static u32_t output_cb(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx)
{
    int sent = 0;
    cellular_ppp_t instance = (cellular_ppp_t)ctx;
    if (!instance)
    {
        LOG_E("ctx is null!");
        return 0;
    }
    /*If ppp is closed, writing data is not allowed.
     *Note: lwip does not know the status of ppp,
     *and may call pppnetif to send data when ppp is closed
     */
    if (!instance->running)
    {
        LOG_W("PPP is closed, can't send data!");
        return 0;
    }
    if (!instance->device)
    {
        LOG_E("Device is unavailable!");
        return 0;
    }

    cellular_device_t dev = rt_container_of(instance, struct cellular_device, ppp);

    cellular_module_wakeup_lock(dev);
    if (instance->device->flag & RT_DEVICE_FLAG_DMA_TX)
    {
        rt_sem_control(&instance->tx_sem, RT_IPC_CMD_RESET, RT_NULL);
    }
    sent = rt_device_write(instance->device, 0, (const char *) data, len);
    if (sent < 0)
    {
        LOG_W("ppp write failed!");
    }
    else
    {
        LOG_D("Write %dB data to modem:", sent);
        PPPD_DUMP(data, len);
        if (len != sent)
        {
            LOG_W("Write data may be loss: %d:%d(need send |has sent)", len, sent);
        }
        instance->tx_bytes += sent;
    }
    if (instance->device->flag & RT_DEVICE_FLAG_DMA_TX)
    {
        if (RT_EOK != rt_sem_take(&instance->tx_sem, rt_tick_from_millisecond(5000)))
        {
            LOG_W("Send data timeout!");
        }
    }
    cellular_module_wakeup_unlock(dev);
    return (sent > 0 ? sent : 0);
}

static int ppp_data_connect(cellular_ppp_t instance)
{
    int ret = RT_EOK;
    uint32_t end_time = 0;
    int rcv = 0;
    snprintf(instance->buffer, sizeof(instance->buffer), "ATD%s\r\n", instance->config->dial_number);
    cellular_device_t dev = rt_container_of(instance, struct cellular_device, ppp);

    cellular_module_wakeup_lock(dev);
    LOG_D("Begein data connect(ATD%s)...", instance->config->dial_number);
    if (instance->device->flag & RT_DEVICE_FLAG_DMA_TX)
    {
        rt_sem_control(&instance->tx_sem, RT_IPC_CMD_RESET, RT_NULL);
    }
    ret = rt_device_write(instance->device, 0, (const char *) instance->buffer, strlen(instance->buffer));
    if (ret <= 0)
    {
        LOG_W("ppp write failed!");
        ret = -RT_ERROR;
        goto exit;
    }

    if (instance->device->flag & RT_DEVICE_FLAG_DMA_TX)
    {
        if (RT_EOK != rt_sem_take(&instance->tx_sem, rt_tick_from_millisecond(1000)))
        {
            LOG_W("Send data timeout!");
            ret = -RT_ERROR;
            goto exit;
        }
    }

    ret = -RT_ETIMEOUT;
    memset(instance->buffer, 0, sizeof(instance->buffer));
    end_time = uptime() + 20;
    while (time_before(uptime(), end_time))
    {
        int r = rt_device_read(instance->device, 0, instance->buffer + rcv, sizeof(instance->buffer) - rcv);
        if (r > 0)
        {
            rcv += r;
        }
        else
        {
            rt_sem_control(&instance->rx_sem, RT_IPC_CMD_RESET, RT_NULL);
            rt_sem_take(&instance->rx_sem, rt_tick_from_millisecond(1000));
        }
        if (rcv > 0 && strstr(instance->buffer, "CONNECT"))
        {
            LOG_D("%s", instance->buffer);
            ret = RT_EOK;
            break;
        }
    }
    if (RT_EOK == ret && ERR_OK == ppp_connect(instance->pcb, 0))
    {
        ret = RT_EOK;
    }
    else
    {
        ret = -RT_ERROR;
        LOG_W("Create data connection failed!");
    }
exit:
    cellular_module_wakeup_unlock(dev);
    return ret;
}
rt_inline int ppp_data_process(cellular_ppp_t instance)
{
    int rcv = 0, ret = 0;
    memset(instance->buffer, 0, sizeof(instance->buffer));
    while (rcv < sizeof(instance->buffer))
    {
        ret = rt_device_read(instance->device, 0, instance->buffer + rcv, sizeof(instance->buffer) - rcv);
        if (ret > 0)
        {
            rcv += ret;
        }
        else
        {
            rt_sem_control(&instance->rx_sem, RT_IPC_CMD_RESET, RT_NULL);
            if (RT_EOK != rt_sem_take(&instance->rx_sem, rt_tick_from_millisecond(100)))
            {
                break;
            }
        }
    }
    if (rcv > 0)
    {
        LOG_D("Rcv %dB data from lvl:", rcv);
        PPPD_DUMP(instance->buffer, rcv);
        LOG_D("ppp netif[%d] rcv %d bytes", instance->pcb->netif->num, rcv);
        instance->rx_bytes += rcv;
#if PPP_INPROC_IRQ_SAFE
        pppos_input(instance->pcb, (u8_t *)instance->buffer, rcv);
#else
        if (pppos_input_tcpip(instance->pcb, (u8_t *)instance->buffer, rcv) != ERR_OK)
        {
            LOG_E("pppos_input_tcpip failed,drop %d bytes", rcv);
            instance->ppp_drop_bytes += rcv;
            instance->ppp_drop_pkts++;
        }
#endif /*PPP_INPROC_IRQ_SAFE*/
    }
    return RT_EOK;
}
/**
 * @brief pppd thread handler
 *
 * @param args
 */
static void pppd_task_handler(void *args)
{
    LOG_I("Start");
    rt_uint32_t rcv_event = 0;
    cellular_ppp_t instance = (cellular_ppp_t) args;
    RT_ASSERT(instance);
    rt_enter_critical();
    instance->running = RT_TRUE;
    rt_exit_critical();
    /*1. re-open device*/
    if (RT_EOK != device_open(instance))
    {
        LOG_E("Can't open %s", instance->device->parent.name);
        instance->running = RT_FALSE;
    }
    else if (RT_EOK != ppp_data_connect(instance))
    {
        LOG_E("Connect pcb failed!");
        instance->running = RT_FALSE;
    }
    else
    {
        instance->running = RT_TRUE;
#if LWIP_NETIF_STATUS_CALLBACK
        netif_set_status_callback(&instance->netif, netif_status_callback);
#endif /* LWIP_NETIF_STATUS_CALLBACK */
    }
    while (instance->running)
    {
        rcv_event = 0;
        /*1. Receive event from other thread and tcpip thread*/
        if (RT_EOK == rt_event_recv(&instance->event,
                                    CELLULAR_PPP_EVENT_NET_UP | CELLULAR_PPP_EVENT_NET_DOWN | CELLULAR_PPP_EVENT_CLOSE_REQ,
                                    RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                                    rt_tick_from_millisecond(5), &rcv_event))
        {
            LOG_D("Rcv event: %x", rcv_event);
            if ((rcv_event & CELLULAR_PPP_EVENT_CLOSE_REQ))
            {
                LOG_W("User request close !");

                if (instance->pcb)
                {
                    /*if ppp is connecting or has been connected, close if safely*/
                    ppp_close(instance->pcb, 0);
                }
                else
                {
                    instance->running = RT_FALSE;
                }

            }
            if (rcv_event & CELLULAR_PPP_EVENT_NET_DOWN)
            {
                LOG_D("pcb-phase: %d", instance->pcb->phase);
                instance->running = RT_FALSE;
            }
            if (instance->user_hook)
            {
                instance->user_hook(rcv_event, rcv_event);
            }
        }
        ppp_data_process(instance);
    }
    device_close(instance);
    instance->running = RT_FALSE;
    instance->pcb->phase = PPP_PHASE_DEAD;
    LOG_I("PPPD closed!");
    if (instance->user_hook)
    {
        LOG_D("exe hook!");
        instance->user_hook(CELLULAR_PPP_EVENT_CLOSED | CELLULAR_PPP_EVENT_NET_DOWN, CELLULAR_PPP_EVENT_CLOSED | CELLULAR_PPP_EVENT_NET_DOWN);
    }
    rt_event_send(&instance->event, CELLULAR_PPP_EVENT_CLOSED);

    LOG_D("Exit.");
}

/* Public function prototypes -----------------------------------------------*/
int cellular_ppp_init(cellular_device_t dev)
{
    RT_ASSERT(dev);
    cellular_ppp_t p = &dev->ppp;
    char name[RT_NAME_MAX] = {0};
    memset(p, 0, sizeof(struct cellular_ppp));
    p->running = RT_FALSE;
    snprintf(name, sizeof(name), "evp_%s", dev->module_info.name);
    rt_event_init(&p->event, name, RT_IPC_FLAG_PRIO);
    snprintf(name, sizeof(name), "rx_sem_%s", dev->module_info.name);
    rt_sem_init(&p->rx_sem, name, 0, RT_IPC_FLAG_PRIO);
    snprintf(name, sizeof(name), "tx_sem_%s", dev->module_info.name);
    rt_sem_init(&p->tx_sem, name, 0, RT_IPC_FLAG_PRIO);

    return CELLULAR_OK;
}


/**
 * @brief start ppp
 *
 * @param dev   pointer to cellular device
 * @param echo_interval  lcp echo interval in seconds
 * @param user_hook     user callback
 * @return int
 */
int cellular_ppp_start(cellular_device_t dev, uint8_t echo_interval, void (*user_hook)(int status, int event))
{
    int err = CELLULAR_OK;
    char name[RT_NAME_MAX] = {0};
    RT_ASSERT(dev != NULL);
    cellular_ppp_t p = &dev->ppp;

    if (p->running && \
            RT_THREAD_CLOSE != (p->thread.stat & RT_THREAD_STAT_MASK) && \
            RT_THREAD_INIT != (p->thread.stat & RT_THREAD_STAT_MASK))
    {
        LOG_W("PPPD is running!");
        return CELLULAR_OK;
    }

    if (!p->pcb)
    {
        LOG_I("Create ppp pcb..");
        p->pcb = pppos_create(&p->netif, output_cb, status_cb, p);
    }
    if (!p->pcb)
    {
        LOG_E("Create ppp pcb failed!");
        return -RT_ERROR;
    }

    p->user_hook = user_hook;
    p->device = rt_device_find(cellular_get_port_name(dev, CELLULAR_PORT_TYPE_PPP));
    if (!p->device)
    {
        LOG_E("Can't find device %s", cellular_get_port_name(dev, CELLULAR_PORT_TYPE_PPP));
        return -RT_EIO;
    }

    /*1. reset event*/
    rt_event_control(&p->event, RT_IPC_CMD_RESET, NULL);
    rt_sem_control(&p->rx_sem, RT_IPC_CMD_RESET, NULL);
    rt_sem_control(&p->tx_sem, RT_IPC_CMD_RESET, NULL);
    /*2. config ppp*/
    p->config = cellular_get_used_ppp_config(dev);
    p->netif.name[0] = 'p';
    p->netif.name[1] = '0';
    ppp_set_neg_pcomp(p->pcb, 0);
    ppp_set_neg_accomp(p->pcb, 0);
    ppp_set_neg_asyncmap(p->pcb, 1);
    ppp_set_asyncmap(p->pcb, 0);
    ppp_set_default(p->pcb);
    ppp_set_usepeerdns(p->pcb, 1);
    p->pcb->settings.lcp_echo_interval = echo_interval;
    p->pcb->settings.lcp_echo_fails = 5;
    if (p->config->auth)
    {
        ppp_set_auth(p->pcb, p->config->auth, p->config->user_name, p->config->password);
    }
    LOG_I("Set ppp auth %s/%s %s", p->config->user_name, p->config->password, \
          PPP_AUTH_TYPE_PAP == p->config->auth ? "PAP" : \
          PPP_AUTH_TYPE_CHAP == p->config->auth ? "CHAP" : \
          PPP_AUTH_TYPE_MSCHAP == p->config->auth ? "MSCHAP" : \
          PPP_AUTH_TYPE_MSCHAP_V2 == p->config->auth ? "MSCHAP-V2" : \
          PPP_AUTH_TYPE_EAP == p->config->auth ? "EAP" : "None");
#if LWIP_NETIF_HOSTNAME
    netif_set_hostname(&p->netif, CELLULAR_NETIF_HOST_NAME);
#endif /*LWIP_NETIF_HOSTNAME*/
#ifdef RT_USING_NETDEV
    uint8_t imei[8] = {0};
    cellular_netdev_del(&p->netdev);
    /*Create netdev and add to netdev list*/
    cellular_netdev_add(&p->netdev, &p->netif);
    netdev_set_default(&p->netdev);
    hexstr2hex(imei, 8, (uint8_t *)dev->module_info.imei);
    cellular_netdev_set_hwaddr(&p->netdev, imei, 8);
#endif /*RT_USING_NETDEV*/
    /*3. Create ppp thread*/
    p->pcb->phase = PPP_PHASE_DEAD;
    snprintf(name, sizeof(name), "ppp_%s", dev->module_info.name);
    err = rt_thread_init(&p->thread, name, pppd_task_handler, p, &p->stack[0],
                         sizeof(p->stack), CELLULAR_PPP_THREAD_PRIO, CELLULAR_PPP_THREAD_TIMESLICE);
    if (RT_EOK != err)
    {
        LOG_E("Init pppd task failed,err=%d", err);
        return err;
    }
    err = rt_thread_startup(&p->thread);
    if (RT_EOK != err)
    {
        LOG_E("Start pppd task failed,err=%d", err);
        return err;
    }

    return err;
}

int cellular_ppp_stop(cellular_device_t dev)
{
    rt_bool_t stopped = RT_FALSE;
    RT_ASSERT(dev != NULL);
    cellular_ppp_t p = &dev->ppp;
    if (p->running)
    {
        LOG_I("Stop pppd...");
        rt_uint32_t rcv_event = 0;
        rt_event_send(&p->event, CELLULAR_PPP_EVENT_CLOSE_REQ);
        if (RT_EOK == rt_event_recv(&p->event, CELLULAR_PPP_EVENT_CLOSED, \
                                    RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(5000), &rcv_event))
        {
            stopped = RT_TRUE;
        }

        if (stopped == RT_FALSE && RT_THREAD_CLOSE != (p->thread.stat & RT_THREAD_STAT_MASK) && \
                RT_THREAD_INIT != (p->thread.stat & RT_THREAD_STAT_MASK))
        {
            rt_thread_detach(&p->thread);
        }

        rt_event_control(&p->event, RT_IPC_CMD_RESET, NULL);
        rt_sem_control(&p->rx_sem, RT_IPC_CMD_RESET, NULL);
        rt_sem_control(&p->tx_sem, RT_IPC_CMD_RESET, NULL);
        p->running = RT_FALSE;
        p->pcb->phase = PPP_PHASE_DEAD;
        device_close(p);
#ifdef RT_USING_NETDEV
        cellular_netdev_del(&p->netdev);
#endif /*RT_USING_NETDEV*/
        return cellular_data_mode(dev, CELLULAR_PORT_TYPE_PPP, 10, (void *)0);
    }

    return CELLULAR_OK;
}


int cellular_ppp_get_used_auth(cellular_device_t dev)
{
    return dev->ppp.config->auth;
}

uint32_t cellular_ppp_get_ip(cellular_device_t dev)
{
    return (int)ntohl(dev->ppp.netif.ip_addr.addr);
}

uint32_t cellular_ppp_get_tx_bytes(cellular_device_t dev)
{
    return dev->ppp.tx_bytes;
}

uint32_t cellular_ppp_get_rx_bytes(cellular_device_t dev)
{
    return dev->ppp.rx_bytes;
}

int cellular_ppp_get_state(cellular_device_t dev)
{
    if (dev->ppp.pcb)
    {
        return (PPP_PHASE_RUNNING == dev->ppp.pcb->phase);
    }
    return 0;
}

const char *cellular_ppp_auth2str(ppp_auth_type_t auth)
{
    const char *auth_str = "None";
    switch (auth)
    {
    case PPP_AUTH_TYPE_PAP:
        auth_str = "PAP";
        break;
    case PPP_AUTH_TYPE_CHAP:
        auth_str = "CHAP";
        break;
    case PPP_AUTH_TYPE_MSCHAP:
        auth_str = "MS-CHAP";
        break;
    case PPP_AUTH_TYPE_MSCHAP_V2:
        auth_str = "MS-CHAP-V2";
        break;
    case PPP_AUTH_TYPE_EAP:
        auth_str = "EAP";
        break;
    case PPP_AUTH_TYPE_NONE:
    default:
        auth_str = "None";
        break;
    }
    return auth_str;
}
/* End of file****************************************************************/

